#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CreateExe.py  – 一键 PyInstaller 打包脚本
已解决：
  ① NumPy 2.x 缺失 _multiarray_umath 等 C 扩展
  ② vgamepad 缺失 ViGEmClient.dll
用法：
  python CreateExe.py          # 打包
  python CreateExe.py clean    # 清理
  python CreateExe.py recreate # 重新生成 spec
"""
import os
import sys
import subprocess
import shutil
from pathlib import Path


# ------------------------------------------------------------------
# 版本号处理函数
# ------------------------------------------------------------------
def increment_version(version_file):
    """读取并递增版本号，然后写回文件"""
    if not version_file.exists():
        print(f"版本文件 {version_file} 不存在")
        return False

    content = version_file.read_text(encoding="utf-8").strip()

    # 解析版本号 (格式: version = "1.0.0")
    if not content.startswith('version = "'):
        print(f"版本文件格式不正确: {content}")
        return False

    # 提取版本号字符串
    version_str = content.split('"')[1]
    version_parts = version_str.split('.')

    # 确保版本号有三部分
    if len(version_parts) != 3:
        print(f"版本号格式不正确: {version_str}")
        return False

    # 尝试将各部分转换为整数
    try:
        major, minor, patch = map(int, version_parts)
    except ValueError:
        print(f"版本号包含非数字字符: {version_str}")
        return False

    # 递增补丁版本号（最后一部分）
    patch += 1
    new_version = f"{major}.{minor}.{patch}"

    # 写回文件
    new_content = f'version = "{new_version}"\n'
    version_file.write_text(new_content, encoding="utf-8")
    print(f"版本号已从 {version_str} 更新到 {new_version}")
    return True


# ------------------------------------------------------------------
# 工具函数
# ------------------------------------------------------------------
def site_packages() -> Path:
    """返回当前解释器的 site-packages 目录"""
    import vgamepad
    return Path(vgamepad.__file__).parent.parent


def find_vigem_dll() -> Path:
    """自动定位 ViGEmClient.dll 绝对路径"""
    dll = site_packages() / "vgamepad" / "win" / "vigem" / "client" / "x64" / "ViGEmClient.dll"
    if not dll.exists():
        raise FileNotFoundError("未找到 ViGEmClient.dll，请确认 vgamepad 已安装且为 64 位")
    return dll


def numpy_binaries() -> list[tuple[str, str]]:
    """收集 numpy.libs 下所有 dll（NumPy 2.x 必备）"""
    np_libs = site_packages() / "numpy.libs"
    if not np_libs.exists():
        return []
    return [(str(dll), ".") for dll in np_libs.glob("*.dll")]


# ------------------------------------------------------------------
# 生成 spec
# ------------------------------------------------------------------
def create_spec_file():
    """生成带 NumPy/ViGEm 依赖的 spec"""
    vigem_dll = find_vigem_dll()
    np_bins = numpy_binaries()

    binaries = [(str(vigem_dll), "vgamepad/win/vigem/client/x64")] + np_bins

    hiddenimports = [
        "vgamepad",
        "vgamepad.VX360Gamepad",
        "vgamepad.XUSB_BUTTON",
        "pynput.keyboard",
        "pynput.mouse",
        "yangke.common.config",
        "auto",
        "input",
        "gamepad",
        "joystick",
        # NumPy 2.x C 扩展
        "numpy",
        "numpy._core._multiarray_umath",
        "numpy._core._multiarray_tests",
        "numpy.linalg._umath_linalg",
    ]

    datas = [
        ("dist/", "dist"),
    ]

    # 检查图标文件是否存在
    script_dir = Path(__file__).parent.absolute()
    icon_path = script_dir / "autoOp.ico"
    if icon_path.exists():
        icon_file = str(icon_path)
        print(f"使用图标文件: {icon_file}")
    else:
        icon_file = None
        print("未找到图标文件 autoOp.ico，将使用默认图标")

    excluded = [
        "torch", "torchvision", "torchaudio", "paddle", "paddleocr", "tensorflow",
        "matplotlib", "scipy", "sklearn", "cv2", "PIL", "nltk", "spacy",
        "seaborn", "plotly", "bokeh", "gensim", "transformers", "huggingface",
    ]

    # 创建管理员权限的 manifest 文件
    manifest_content = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>'''

    manifest_path = Path("autoOp.manifest")
    manifest_path.write_text(manifest_content, encoding="utf-8")
    print("已创建管理员权限 manifest 文件")

    spec_txt = f'''# -*- mode: python ; coding: utf-8 -*-
block_cipher = None

a = Analysis(
    ['main.py'],
    pathex=['.'],
    binaries={binaries!r},
    datas={datas!r},
    hiddenimports={hiddenimports!r},
    hookspath=[],
    hooksconfig={{}},
    runtime_hooks=[],
    excludes={excluded!r},
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    a.zipfiles,
    [],
    name='autoOp',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx_exclude=[],
    console=False,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    uac_admin=True,
    uac_uiaccess=False,
    icon={icon_file!r},
)

coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx_exclude=[],
    name='autoOp'
)
'''

    Path("autoOp.spec").write_text(spec_txt, encoding="utf-8")
    print("已创建含 NumPy/ViGEm 依赖的 spec 文件")


# ------------------------------------------------------------------
# 以下逻辑完全沿用你原来的框架，仅调用处改为新 create_spec_file
# ------------------------------------------------------------------
def build_executable():
    print("开始构建可执行文件...")
    script_dir = Path(__file__).parent.absolute()
    os.chdir(script_dir)
    print(f"当前工作目录: {script_dir}")

    # 在打包前更新版本号
    version_file = script_dir / "version.py"
    if not increment_version(version_file):
        print("版本号更新失败，继续打包过程")

    spec_file = script_dir / "autoOp.spec"
    if not spec_file.exists():
        print("创建新的spec文件...")
        create_spec_file()

    output_dir = script_dir / "OutputExe"
    output_dir.mkdir(exist_ok=True)
    print(f"输出目录: {output_dir}")

    old_output = output_dir / "autoOp"
    if old_output.exists():
        print(f"删除旧的输出目录: {old_output}")
        shutil.rmtree(old_output, ignore_errors=True)

    cmd = [sys.executable, "-m", "PyInstaller", "--distpath", str(output_dir), "-y", str(spec_file)]
    print("执行命令:", " ".join(cmd))
    try:
        result = subprocess.run(cmd, check=True, capture_output=True, text=True)
        print(result.stdout)
        if result.stderr:
            print(result.stderr)
        print("构建成功完成!")
        return True
    except subprocess.CalledProcessError as e:
        print(f"构建失败: {e}\n{e.stderr}")
        return False
    except FileNotFoundError:
        print("未找到 PyInstaller，请先 pip install pyinstaller")
        return False


def clean_build():
    print("清理构建文件...")
    script_dir = Path(__file__).parent.absolute()
    for artifact in [script_dir / "build", script_dir / "autoOp.spec", script_dir / "OutputExe",
                     script_dir / "autoOp.manifest"]:
        if artifact.exists():
            shutil.rmtree(artifact) if artifact.is_dir() else artifact.unlink()
            print(f"已删除: {artifact}")
    for p in script_dir.rglob("__pycache__"):
        shutil.rmtree(p)
    print("清理完成!")


def main():
    print("AutoOp 打包脚本", "=" * 50, sep="\n")
    clean_build()
    Path("autoOp.spec").unlink(missing_ok=True)
    create_spec_file()

    success = build_executable()
    if success:
        print("\n" + "=" * 50 + "\n打包完成！可执行文件位于 OutputExe/autoOp 目录中")
    else:
        sys.exit(1)


if __name__ == "__main__":
    main()
